<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Puppeteer 爬虫控制台</title>
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
      rel="stylesheet"
    />
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: "Roboto", "Segoe UI", sans-serif;
      }

      body {
        background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
        color: #333;
        min-height: 100vh;
        padding: 20px;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }

      header {
        text-align: center;
        margin-bottom: 30px;
        padding: 20px;
        background: rgba(255, 255, 255, 0.85);
        border-radius: 15px;
        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
      }

      h1 {
        color: #2c3e50;
        font-size: 2.8rem;
        margin-bottom: 10px;
        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
      }

      .subtitle {
        color: #3498db;
        font-size: 1.4rem;
        font-weight: 300;
        margin-bottom: 20px;
      }

      .main-content {
        display: flex;
        gap: 25px;
        flex-wrap: wrap;
      }

      .control-panel {
        flex: 1;
        min-width: 300px;
        background: rgba(255, 255, 255, 0.92);
        border-radius: 15px;
        padding: 25px;
        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
      }

      .result-panel {
        flex: 2;
        min-width: 400px;
        background: rgba(255, 255, 255, 0.92);
        border-radius: 15px;
        padding: 25px;
        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
      }

      .panel-title {
        font-size: 1.6rem;
        color: #2c3e50;
        margin-bottom: 20px;
        padding-bottom: 10px;
        border-bottom: 2px solid #3498db;
      }

      .form-group {
        margin-bottom: 20px;
      }

      label {
        display: block;
        margin-bottom: 8px;
        font-weight: 500;
        color: #2c3e50;
      }

      input,
      select,
      textarea {
        width: 100%;
        padding: 12px 15px;
        border: 1px solid #ddd;
        border-radius: 8px;
        font-size: 16px;
        background: #f8f9fa;
        transition: all 0.3s;
      }

      input:focus,
      select:focus,
      textarea:focus {
        outline: none;
        border-color: #3498db;
        box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
      }

      button {
        background: #3498db;
        color: white;
        border: none;
        padding: 14px 25px;
        font-size: 18px;
        border-radius: 8px;
        cursor: pointer;
        width: 100%;
        transition: all 0.3s;
        font-weight: 500;
        letter-spacing: 0.5px;
        text-transform: uppercase;
      }

      button:hover {
        background: #2980b9;
        transform: translateY(-2px);
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
      }

      .btn-secondary {
        background: #2c3e50;
        margin-top: 10px;
      }

      .btn-secondary:hover {
        background: #1a252f;
      }

      .result-content {
        height: 450px;
        overflow-y: auto;
        background: #f8f9fa;
        border-radius: 8px;
        padding: 20px;
        border: 1px solid #ddd;
        margin-top: 10px;
      }

      .screenshot-container {
        margin-top: 25px;
        text-align: center;
      }

      .screenshot {
        max-width: 100%;
        border-radius: 8px;
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
        border: 1px solid #ddd;
      }

      .status-bar {
        margin-top: 20px;
        padding: 15px;
        background: #e3f2fd;
        border-radius: 8px;
        text-align: center;
        font-weight: 500;
        color: #1565c0;
      }

      .loading {
        display: none;
        text-align: center;
        margin-top: 20px;
      }

      .spinner {
        border: 4px solid rgba(0, 0, 0, 0.1);
        border-left-color: #3498db;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        animation: spin 1s linear infinite;
        margin: 0 auto 15px;
      }

      @keyframes spin {
        to {
          transform: rotate(360deg);
        }
      }

      .data-item {
        margin-bottom: 15px;
        padding-bottom: 15px;
        border-bottom: 1px solid #eee;
      }

      .data-title {
        font-weight: 600;
        color: #2c3e50;
        margin-bottom: 5px;
        font-size: 18px;
      }

      .data-content {
        color: #555;
        line-height: 1.6;
      }

      .code-block {
        background: #2d3436;
        color: #dfe6e9;
        padding: 15px;
        border-radius: 8px;
        font-family: monospace;
        margin: 20px 0;
        overflow-x: auto;
      }

      @media (max-width: 768px) {
        .main-content {
          flex-direction: column;
        }

        .control-panel,
        .result-panel {
          min-width: 100%;
        }

        h1 {
          font-size: 2.2rem;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <header>
        <h1>Node.js 爬虫控制台</h1>
        <div class="subtitle">使用 Puppeteer 的高级网页抓取工具</div>
      </header>

      <div class="main-content">
        <div class="control-panel">
          <h2 class="panel-title">爬虫控制面板</h2>

          <div class="form-group">
            <label for="targetUrl">目标 URL</label>
            <input
              type="url"
              id="targetUrl"
              placeholder="https://example.com"
              value="https://news.baidu.com"
            />
          </div>

          <div class="form-group">
            <label for="selector">CSS 选择器 (可选)</label>
            <input type="text" id="selector" placeholder=".headlines .title" />
          </div>

          <div class="form-group">
            <label for="mode">浏览器模式</label>
            <select id="mode">
              <option value="headless">无头模式 (推荐)</option>
              <option value="headed">有头模式 (调试)</option>
            </select>
          </div>

          <div class="form-group">
            <label for="contentType">内容类型</label>
            <select id="contentType">
              <option value="text">文本内容</option>
              <option value="html">HTML 结构</option>
            </select>
          </div>

          <button id="startBtn">开始爬取</button>
          <button id="clearBtn" class="btn-secondary">清除结果</button>

          <div class="loading" id="loadingIndicator">
            <div class="spinner"></div>
            <p>正在抓取网页数据，请稍候...</p>
          </div>
        </div>

        <div class="result-panel">
          <h2 class="panel-title">抓取结果</h2>

          <div class="status-bar" id="statusBar">等待开始爬取任务...</div>

          <div class="result-content" id="resultContent">
            <!-- 抓取结果将显示在这里 -->
          </div>

          <div class="screenshot-container">
            <img
              id="screenshot"
              class="screenshot"
              src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='450' viewBox='0 0 800 450'%3E%3Crect width='800' height='450' fill='%23f0f0f0'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999' font-family='Arial'%3E网页截图将显示在此处%3C/text%3E%3C/svg%3E"
              alt="网页截图预览"
            />
          </div>
        </div>
      </div>

      <div class="code-block">
        // Puppeteer 爬虫示例代码<br /><br />
        const puppeteer = require('puppeteer');<br /><br />
        async function scrapeWebsite(url, selector, headless = true) {<br />
        &nbsp;&nbsp;const browser = await puppeteer.launch({ headless });<br />
        &nbsp;&nbsp;const page = await browser.newPage();<br />
        &nbsp;&nbsp;await page.goto(url, { waitUntil: 'networkidle2' });<br /><br />
        &nbsp;&nbsp;// 获取页面信息<br />
        &nbsp;&nbsp;const title = await page.title();<br />
        &nbsp;&nbsp;const description = await
        page.$eval('meta[name="description"]', el => el.content);<br /><br />
        &nbsp;&nbsp;// 获取特定元素内容<br />
        &nbsp;&nbsp;let content = '';<br />
        &nbsp;&nbsp;if (selector) {<br />
        &nbsp;&nbsp;&nbsp;&nbsp;content = await page.$eval(selector, el =>
        el.textContent);<br />
        &nbsp;&nbsp;}<br /><br />
        &nbsp;&nbsp;// 截取屏幕截图<br />
        &nbsp;&nbsp;await page.screenshot({ path: 'screenshot.png' });<br /><br />
        &nbsp;&nbsp;await browser.close();<br /><br />
        &nbsp;&nbsp;return { title, description, content };<br />
        }
      </div>
    </div>

    <script>
      document.addEventListener("DOMContentLoaded", function () {
        const startBtn = document.getElementById("startBtn");
        const clearBtn = document.getElementById("clearBtn");
        const targetUrl = document.getElementById("targetUrl");
        const selector = document.getElementById("selector");
        const mode = document.getElementById("mode");
        const contentType = document.getElementById("contentType");
        const resultContent = document.getElementById("resultContent");
        const statusBar = document.getElementById("statusBar");
        const screenshot = document.getElementById("screenshot");
        const loadingIndicator = document.getElementById("loadingIndicator");

        // 模拟爬取结果
        const mockData = {
          title: "百度新闻——海量中文资讯平台",
          description:
            "百度新闻是包含海量资讯的新闻服务平台，真实反映每时每刻的新闻热点。",
          content:
            "热点要闻\n国际大事件：全球领导人峰会今日召开\n科技前沿：新型量子计算机突破计算瓶颈\n财经观察：股市迎来新一轮上涨周期\n体育快报：奥运健儿备战巴黎奥运会",
          screenshot:
            "https://via.placeholder.com/800x450/3498db/ffffff?text=百度新闻截图",
        };

        startBtn.addEventListener("click", async function () {
          // 显示加载指示器
          loadingIndicator.style.display = "block";
          statusBar.textContent = "正在连接到目标网站...";
          resultContent.innerHTML = "";

          // 模拟网络请求延迟
          await new Promise((resolve) => setTimeout(resolve, 1500));

          // 模拟数据抓取
          statusBar.textContent = "正在解析网页内容...";
          await new Promise((resolve) => setTimeout(resolve, 1000));

          statusBar.textContent = "提取目标内容...";
          await new Promise((resolve) => setTimeout(resolve, 800));

          // 显示结果
          loadingIndicator.style.display = "none";
          statusBar.textContent = `抓取完成: ${targetUrl.value}`;
          statusBar.style.backgroundColor = "#e8f5e9";
          statusBar.style.color = "#2e7d32";

          // 显示结果
          resultContent.innerHTML = `
                    <div class="data-item">
                        <div class="data-title">页面标题</div>
                        <div class="data-content">${mockData.title}</div>
                    </div>
                    <div class="data-item">
                        <div class="data-title">页面描述</div>
                        <div class="data-content">${mockData.description}</div>
                    </div>
                    <div class="data-item">
                        <div class="data-title">提取的内容</div>
                        <div class="data-content">${mockData.content}</div>
                    </div>
                `;

          // 更新截图
          screenshot.src = mockData.screenshot;
          screenshot.alt = mockData.title + " 截图";
        });

        clearBtn.addEventListener("click", function () {
          resultContent.innerHTML = "";
          statusBar.textContent = "结果已清除，等待新的爬取任务...";
          statusBar.style.backgroundColor = "#e3f2fd";
          statusBar.style.color = "#1565c0";
          screenshot.src =
            "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='450' viewBox='0 0 800 450'%3E%3Crect width='800' height='450' fill='%23f0f0f0'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999' font-family='Arial'%3E网页截图将显示在此处%3C/text%3E%3C/svg%3E";
        });
      });
    </script>
  </body>
</html>
